/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * VaribleCellrenderer.java
 *
 * Created on Jun 7, 2010, 10:41:18 AM
 */

package org.netbeans.modules.javafx.debugger.tablerendering;

import com.sun.javafx.jdi.FXField;
import com.sun.javafx.jdi.FXValue;
import com.sun.javafx.jdi.FXSequenceReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.Value;
import java.awt.Component;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import org.netbeans.api.debugger.jpda.Field;
import org.netbeans.api.debugger.jpda.JPDAClassType;
import org.netbeans.modules.debugger.jpda.expr.JDIVariable;
import org.netbeans.modules.debugger.jpda.models.AbstractVariable;
import org.netbeans.modules.debugger.jpda.models.JPDAClassTypeImpl;
import org.netbeans.modules.debugger.jpda.models.JPDAThreadImpl;
import org.openide.util.RequestProcessor;

/**
 *
 * @author Michal Skvor <michal.skvor at sun.com>
 */
public class VariableCellRenderer extends javax.swing.JPanel implements TableCellRenderer, TableCellEditor {

    private static final boolean evaluateImmediate = false;

    private Object o;
    private String columnName;

    private final Set<CellEditorListener> cellEditorListeners = new HashSet<CellEditorListener>();
    
    private RequestProcessor rp = new RequestProcessor( "", 5 );
    private JTable table;
    private int row, column;
    private boolean repaintCall = false;
    
    /** Creates new form VaribleCellrenderer */
    public VariableCellRenderer( Object o, String columnName ) {
        this.o = o;
        this.columnName = columnName;
        
        initComponents();
    }

    public void setObject( Object o ) {
        this.o = o;
    }
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        labelValue = new javax.swing.JLabel();
        buttonUpdate = new javax.swing.JButton();
        jButton1 = new javax.swing.JButton();

        setLayout(new java.awt.GridBagLayout());

        labelValue.setBackground(javax.swing.UIManager.getDefaults().getColor("Table.focusCellBackground"));
        labelValue.setText(org.openide.util.NbBundle.getMessage(VariableCellRenderer.class, "VariableCellRenderer.labelValue.text")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.ipadx = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.ABOVE_BASELINE;
        gridBagConstraints.weightx = 1.0;
        add(labelValue, gridBagConstraints);

        buttonUpdate.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/netbeans/modules/javafx/debugger/resources/redo.png"))); // NOI18N
        buttonUpdate.setText(org.openide.util.NbBundle.getMessage(VariableCellRenderer.class, "VariableCellRenderer.buttonUpdate.text")); // NOI18N
        buttonUpdate.setToolTipText(org.openide.util.NbBundle.getMessage(VariableCellRenderer.class, "VariableCellRenderer.buttonUpdate.toolTipText")); // NOI18N
        buttonUpdate.setMaximumSize(new java.awt.Dimension(32, 32));
        buttonUpdate.setMinimumSize(new java.awt.Dimension(32, 20));
        buttonUpdate.setPreferredSize(new java.awt.Dimension(32, 32));
        buttonUpdate.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                buttonUpdateActionPerformed(evt);
            }
        });
        add(buttonUpdate, new java.awt.GridBagConstraints());

        jButton1.setText(org.openide.util.NbBundle.getMessage(VariableCellRenderer.class, "VariableCellRenderer.jButton1.text")); // NOI18N
        jButton1.setMaximumSize(new java.awt.Dimension(16, 20));
        jButton1.setMinimumSize(new java.awt.Dimension(16, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        add(jButton1, gridBagConstraints);
    }// </editor-fold>//GEN-END:initComponents

    private void buttonUpdateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonUpdateActionPerformed
        if( o instanceof Field ) {      
            rp.post( new ValueEvaluator((Field)o ));
        }
    }//GEN-LAST:event_buttonUpdateActionPerformed

    public Component getTableCellRendererComponent( JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column )
    {
        this.table = table;
        this.row = row;
        this.column = column;
        
//        System.out.println(" getTableCellRendererComponent " + value + ", " + row + ", " + column );
        if( o instanceof Field ) {
            if( !repaintCall ) {
                rp.post( new ValueEvaluator((Field)o ));
            } else {
                repaintCall = false;
            }
        } else {
            labelValue.setText( value.toString());
        }
        if( isSelected ) {
            setBackground( table.getSelectionBackground());
            setForeground( table.getSelectionForeground());
            labelValue.setBackground( table.getSelectionBackground());
            labelValue.setForeground( table.getSelectionForeground());
        } else {
            setBackground( table.getBackground());
            setForeground( table.getForeground());
            labelValue.setBackground( table.getBackground());
            labelValue.setForeground( table.getForeground());
        }

        return this;
    }
    
    /**
     * Task which evaluates value from JDI in separate thread from AWT
     */
    private class ValueEvaluator implements Runnable {
        
        Field field;
        
        ValueEvaluator( Field field ) {
            this.field = field;
        }
        
        private void update() {
            repaintCall = true;
            Rectangle r = table.getCellRect( row, column, true );                   
            table.repaint( r );
        }
        
        public void run() {
            
            Field f = (Field)o;
            JDIVariable v = (JDIVariable)f;
            AbstractVariable av = (AbstractVariable)f;
            
            boolean bound = false;
            boolean invalid = false;
            
            JPDAThreadImpl thread = (JPDAThreadImpl)av.getDebugger().getCurrentThread();  
            if( thread == null ) {
                buttonUpdate.setVisible( false );
                labelValue.setText( "No current thread." );
                update();
                return;
            }
            thread.accessLock.writeLock().lock();
            
            try {            
                if( !thread.isSuspended()) {
                    buttonUpdate.setVisible( false );
                    labelValue.setText( "No current thread." );
                    update();
                    return;
                }
            

                JPDAClassType ref = f.getDeclaringClass();            
                JPDAClassTypeImpl refi = (JPDAClassTypeImpl)ref;
                ReferenceType referenceType = refi.getType();
                FXField field = (FXField)referenceType.fieldByName( f.getName());
                if( field != null ) {
                    bound = field.declaringType().isBound( field );
                    invalid = field.declaringType().isInvalid( field );
                }

                buttonUpdate.setVisible( bound );

                if( !invalid || evaluateImmediate ) {
                    Value oo = v.getJDIValue();
                    FXValue fxv = (FXValue)oo;
                    labelValue.setText( f.getValue());
                    if( fxv instanceof FXSequenceReference ) {
                        FXSequenceReference sref = (FXSequenceReference)fxv;
                        labelValue.setText( labelValue.getText() + "(length= " + sref.length() + ")" );
                    }
                } else {
                    labelValue.setText( "Invalid value" );
                }
            } finally {
                thread.accessLock.writeLock().unlock();
            }            
            update();
        }
        
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton buttonUpdate;
    private javax.swing.JButton jButton1;
    private javax.swing.JLabel labelValue;
    // End of variables declaration//GEN-END:variables

    private JTextField textValue = new JTextField();

    public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column ) {
        if( o instanceof Field ) {
            Field f = (Field)o;
            JDIVariable v = (JDIVariable)f;
            Value oo = v.getJDIValue();
            FXValue fxv = (FXValue)oo;

            boolean bound = false;
            boolean invalid = false;
            
            if( oo == null ) {
                textValue.setText( "null" ); // NOI18N
                return textValue;
            }
            List<ReferenceType> references = oo.virtualMachine().classesByName( f.getClassName());
            if( references.size() > 0  ) {
                ReferenceType ref = references.get( 0 );
                if( ref != null ) {
                    FXField field = (FXField)ref.fieldByName( f.getName());
                    if( field != null ) {
                        bound = field.declaringType().isBound( field );
                        invalid = field.declaringType().isInvalid( field );
                    }
                }
            }
            
            if( bound || invalid || fxv instanceof FXSequenceReference ) {
                return this;
            }
            textValue.setText( f.getValue());
        }
        return textValue;
    }

    public Object getCellEditorValue() {
        if( textValue != null ) {
            return textValue.getText();
        }
        return null;
    }

    public boolean isCellEditable( EventObject anEvent ) {
        return true;
    }

    public boolean shouldSelectCell( EventObject anEvent ) {
        return true;
    }

    public boolean stopCellEditing() {
        final Set<CellEditorListener> listeners = Collections.unmodifiableSet( cellEditorListeners );
        synchronized( listeners ) {
            for( CellEditorListener l : listeners ) {
                ChangeEvent e = new ChangeEvent( this );
                l.editingStopped( e );
            }
        }
        return true;
    }

    public void cancelCellEditing() {
        final Set<CellEditorListener> listeners = Collections.unmodifiableSet( cellEditorListeners );
        synchronized( listeners ) {
            for( CellEditorListener l : listeners ) {
                ChangeEvent e = new ChangeEvent( this );
                l.editingCanceled( e );
            }
        }
    }

    public void addCellEditorListener( CellEditorListener l ) {
        cellEditorListeners.add( l );
    }

    public void removeCellEditorListener( CellEditorListener l ) {
        cellEditorListeners.remove( l );
    }
}
